"""Config flow for LuciConfig."""
from copy import deepcopy

import asyncio
import logging
import re
from typing import Any, Dict, Optional

from openwrt_luci_rpc.openwrt_luci_rpc import OpenWrtLuciRPC  # pylint: disable=import-error
from openwrt_luci_rpc.exceptions import LuciConfigError, InvalidLuciTokenError, InvalidLuciLoginError  # pylint: disable=import-error

import voluptuous as vol

from homeassistant import config_entries, exceptions
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import selector
from homeassistant.helpers.entity_registry import (
    async_entries_for_config_entry,
    async_get,
)

from homeassistant.const import (  # pylint: disable=import-error
    CONF_NAME,
    CONF_HOST,
    CONF_PASSWORD,
    CONF_SSL,
    CONF_USERNAME,
    CONF_VERIFY_SSL,
    CONF_SCAN_INTERVAL,
    CONF_MAC,
)

from .const import (
    DOMAIN,
    DEFAULT_SSL,
    DEFAULT_VERIFY_SSL,
    DEFAULT_UPDATE_INTERVAL,
    CONN_TIMEOUT,
    CONF_BLACKLIST,
)
_LOGGER = logging.getLogger(__name__)

RESULT_CONN_ERROR = "cannot_connect"
RESULT_LOG_MESSAGE = {RESULT_CONN_ERROR: "Connection error"}

CONNECT_SCHEMA = vol.Schema(
    {
        vol.Required(CONF_HOST): cv.string,
        vol.Required(CONF_USERNAME): cv.string,
        vol.Required(CONF_PASSWORD): cv.string,
        vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
        vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
        vol.Required(CONF_SCAN_INTERVAL, default=DEFAULT_UPDATE_INTERVAL): selector.NumberSelector(
            selector.NumberSelectorConfig(
                min=1,
                max=60,
                step=1,
                mode=selector.NumberSelectorMode.BOX,
            ),
        ),
    }
)
MAC_SCHEMA = vol.Schema(
    {
        vol.Required(CONF_NAME): cv.string,
        vol.Required(CONF_MAC): cv.string,
        vol.Optional("add_another"): cv.boolean,
    }
)


async def validate_input(user_input: Optional[Dict[str, Any]] = None) -> Dict[str, str] | None:
    if user_input is None:
        return None

    name = user_input[CONF_NAME]

    macth_name = re.match(r"^([A-Z0-9a-z_]{2,})$", name)
    if not macth_name:
        raise NameIsIncorrect

    mac = user_input[CONF_MAC]
    macth_mac = re.match(r"^([A-F0-9a-f]{2}:){5}[A-F0-9a-f]{2}$", mac)
    if not macth_mac:
        raise ValueError

    dict_t = {}

    dict_t[CONF_NAME] = "ha_" + name + "_" + mac.strip().replace(":", "")[6:12]
    dict_t[CONF_MAC] = mac

    return dict_t


def _try_connect(user_input: Optional[Dict[str, Any]]) -> None:
    """检查路由器是否可以链接。

    如果配置无效，则抛出 CannotConnect 异常。
    """
    try:
        rpc = OpenWrtLuciRPC(str(user_input[CONF_HOST]),
                             user_input[CONF_USERNAME],
                             user_input[CONF_PASSWORD],
                             user_input[CONF_SSL],
                             user_input[CONF_VERIFY_SSL])
        success_init = rpc.token is not None
        if not success_init:
            raise CannotConnect
    except (LuciConfigError, InvalidLuciTokenError, InvalidLuciLoginError) as e:
        raise CannotConnect
    except:
        raise HostIsUnreachable


class LuciConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
    """Config flow for LuciConfig component."""

    VERSION = 1
    data: Optional[Dict[str, Any]]

    async def async_step_user(self, user_input: Optional[Dict[str, Any]] = None):
        """当用户通过用户界面启动流时调用。"""

        errors: Dict[str, str] = {}

        if user_input is not None:
            try:
                await asyncio.wait_for(
                    self.hass.async_add_executor_job(_try_connect, user_input),
                    timeout=CONN_TIMEOUT,
                )
            except CannotConnect:
                errors["base"] = "cannot_connect"
            except HostIsUnreachable:
                errors["base"] = "host_is_unreachable"

            if not errors:
                self.data = user_input
                self.data[CONF_BLACKLIST] = []

                return await self.async_step_blacklist()

        return self.async_show_form(step_id="user", data_schema=CONNECT_SCHEMA, errors=errors)

    async def async_step_blacklist(self, user_input: Optional[Dict[str, Any]] = None):
        """Second step in config flow to add a MAC address to blacklist."""

        errors: Dict[str, str] = {}
        if user_input is not None:
            _LOGGER.debug("USER INPUT:  ==> %s", dict(user_input))

            try:
                dd = await validate_input(user_input)
                if dd is not None:
                    self.data[CONF_BLACKLIST].append(dd)
            except ValueError:
                errors["base"] = "mac_address"
            except NameIsIncorrect:
                errors["base"] = "name_is_incorrect"

            if not errors:
                _LOGGER.debug("user_input.get(CONF_NAME, user_input[CONF_MAC]) =:> %s", user_input.get(
                    CONF_NAME, user_input[CONF_MAC]))
                # If user ticked the box show this form again so they can add an
                # additional mac.
                if user_input.get("add_another", False):
                    return await self.async_step_blacklist()

                    # User is done adding blacklist, create the config entry.
                return self.async_create_entry(title=DOMAIN + "_" + self.data[CONF_HOST].replace(".", "")[6:12], data=self.data)

        return self.async_show_form(
            step_id="blacklist", data_schema=MAC_SCHEMA, errors=errors
        )

    @staticmethod
    @callback
    def async_get_options_flow(config_entry):
        """Get the options flow for this handler."""
        return LuciConfigOptionsFlowHandler(config_entry)


class LuciConfigOptionsFlowHandler(config_entries.OptionsFlow):
    """Handles Option flow for LuciConfig component."""

    def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        # self.config = dict(config_entry.data)

    async def async_step_init(self, user_input: dict[str, Any] | None = None) -> Dict[str, Any]:
        """Manage the options for the custom component."""

        errors: Dict[str, str] = {}
        # 从实体注册表中获取所有已配置的设备，以便我们可以填充多选下拉列表，
        # 该下拉列表将允许用户删除现有设备。

        entity_registry = async_get(self.hass)
        entries = async_entries_for_config_entry(
            entity_registry, self.config_entry.entry_id
        )
        # Default value for our multi-select.
        all_rules = {e.entity_id: e.original_name for e in entries}
        rule_map = {e.entity_id: e for e in entries}

        if user_input is not None:
            _LOGGER.debug("CONFIG_FLOW, USER INPUT:  ==> %s", dict(user_input))
            _LOGGER.debug("CONFIG_FLOW, config_entry.data:  ==> %s",
                          dict(self.config_entry.data))
            _LOGGER.debug("CONFIG_FLOW, updated_repos = config_entry.data[CONF_BLACKLIST]:  ==> %s",
                          self.config_entry.data[CONF_BLACKLIST])
            _LOGGER.debug(
                "CONFIG_FLOW, user_input['blacklist']:  ==> %s", user_input["blacklist"])

            updated_repos = deepcopy(
                self.config_entry.data[CONF_BLACKLIST])

            # Remove any unchecked MAC.
            removed_entities = [
                entity_id
                for entity_id in rule_map.keys()
                if entity_id not in user_input["blacklist"]
            ]
            for entity_id in removed_entities:
                # Unregister from HA
                entity_registry.async_remove(entity_id)
                # Remove from our configured repos.
                entry = rule_map[entity_id]
                _LOGGER.debug(
                    "CONFIG_FLOW, entry.original_name:  ==> %s", entry.original_name)
                _LOGGER.debug(
                    "CONFIG_FLOW, entry.unique_id:  ==> %s", entry.unique_id)
                _LOGGER.debug(
                    "CONFIG_FLOW, entry.entity_id:  ==> %s", entry.entity_id)

                entry_uniqueid = entry.unique_id
                updated_repos = [
                    e for e in updated_repos if e["name"] != entry_uniqueid[entry_uniqueid.index('_ha_')+1:]]

            if user_input.get(CONF_MAC):
                try:
                    dd = await validate_input(user_input)
                    if dd is not None:
                        updated_repos.append(dd)
                except ValueError:
                    errors["base"] = "mac_address"
                except NameIsIncorrect:
                    errors["base"] = "name_is_incorrect"

            if not errors:
                # Value of data will be set on the options property of our config_entry instance.
                config = dict(self.config_entry.data)
                config[CONF_BLACKLIST] = updated_repos
                _LOGGER.debug(
                    "CONFIG_FLOW, updated_repos:  ==> %s", updated_repos)
                self.hass.config_entries.async_update_entry(
                    self.config_entry, data=config, options={CONF_BLACKLIST: updated_repos})

                return self.async_create_entry(
                    title=DOMAIN +
                    self.config_entry.data[CONF_HOST].replace(":", "")[6:12],
                    data={CONF_BLACKLIST: updated_repos},
                )

        options_schema = vol.Schema(
            {
                vol.Optional("blacklist", default=list(all_rules.keys())): cv.multi_select(
                    all_rules
                ),
                vol.Optional(CONF_NAME): cv.string,
                vol.Optional(CONF_MAC): cv.string,
            }
        )

        return self.async_show_form(
            step_id="init", data_schema=options_schema, errors=errors
        )


class CannotConnect(exceptions.HomeAssistantError):
    """Error to indicate we can not connect."""


class NameIsIncorrect(exceptions.HomeAssistantError):
    """Error to indicate we can not connect."""


class HostIsUnreachable(exceptions.HomeAssistantError):
    """Error to indicate we can not connect."""
